# -*- coding: utf-8 -*-
# @Time    : 2024/7/17 16:20
# @Author  : yujiahao
# @File    : 37_fastapi_sql_database.py
# @description:SQL (关系型) 数据库，创建数据库连接(以MySQL为例)。这个文件是演示的其中一个，这个例子蛮好的。


"""
SQL (关系型) 数据库：
---------------------
 SQLAlchemy 支持的数据库，如：
---------------------------------------------------------------
1. PostgreSQL
2. MySQL
3. SQLite
4. Oracle
5. Microsoft SQL Server，等等其它数据库

在此示例中，我们将使用 【MySQL】。


Tip:
-----
这儿有一个 FastAPI 和 PostgreSQL 的官方项目生成器，全部基于 Docker，包括前端和更多工具：
https://github.com/tiangolo/full-stack-fastapi-postgresql

Note:
------
请注意，大部分代码是 SQLAlchemy 的标准代码，可以用于任何框架。FastAPI 特定的代码和往常一样少。



ORMs（对象关系映射）：就是用面向对象的方式代替SQL去处理数据库的数据，下面都不用看。

ORM（Object-Relational Mapping，面向对象关系映射）是一种技术，通过将对象编程语言中的对象与关系数据库中的表进行映射，
使得开发者可以使用面向对象的方式来操作数据库，而不需要直接编写 SQL 语句。ORM 使得数据库操作更加直观和简洁，增强了代码的可维护性和可读性。

ORM 的主要特点：

1. 对象与数据库表的映射：
   - ORM 将编程语言中的类与数据库表进行映射，将类的实例与数据库表中的记录进行映射。
   - 类的属性对应数据库表的列，类的实例对应数据库表中的行。

2. 面向对象的数据库操作：
   - 开发者可以通过操作对象来进行数据库的增删改查操作，而不需要直接编写 SQL 语句。
   - ORM 框架会自动将对象操作转换为相应的 SQL 语句并执行。

3. 简化数据库操作：
   - ORM 提供了丰富的 API 来简化常见的数据库操作，如插入、更新、删除、查询等。
   - 通过 ORM，可以减少重复的数据库操作代码，提高开发效率。

4. 数据库独立性：
   - ORM 框架通常支持多种数据库，可以在不修改代码的情况下切换数据库。
   - 这使得应用程序具有更好的数据库独立性和可移植性。

常见的 ORM 框架：

- Python:
  - SQLAlchemy
  - Django ORM
- Java:
  - Hibernate
- C#:
  - Entity Framework
- PHP:
  - Doctrine
  - Eloquent (Laravel)


-------------------------
FastAPI 可与任何数据库在任何样式的库中一起与数据库进行通信。

一种常见的模式是使用“ORM”：对象关系映射。

ORM 具有在代码和数据库表（“关系型”）中的对象之间转换（“映射”）的工具。

使用 ORM，您通常会在 SQL 数据库中创建一个代表映射的类，该类的每个属性代表一个列，具有名称和类型。

例如，一个类 Pet 可以表示一个 SQL 表 pets。

该类的每个实例对象都代表数据库中的一行数据。

例如，一个对象 orion_cat（Pet 的一个实例）可以有一个属性 orion_cat.type，对标数据库中的 type 列。并且该属性的值可以是例如 "cat"。

这些 ORM 还具有在表或实体之间建立关系的工具（比如创建多表关系）。

这样，您还可以拥有一个属性 orion_cat.owner，它包含该宠物所有者的数据，这些数据取自另外一个表。

因此，orion_cat.owner.name 可能是该宠物主人的姓名（来自表 owners 中的列 name）。

它可能有一个像 "Arquilian"（一种业务逻辑）。

当您尝试从您的宠物对象访问它时，ORM 将完成所有工作以从相应的表所有者那里再获取信息。

常见的 ORM 例如：Django-ORM（Django 框架的一部分）、SQLAlchemy ORM（SQLAlchemy 的一部分，独立于框架）和 Peewee（独立于框架）等。

在这里，演示一下如何使用 SQLAlchemy ORM。

以类似的方式，也可以使用任何其他 ORM。


文件结构：
-----------
对于这些示例，假设有一个名为 my_super_project 的目录，其中包含一个名为 my_fastapi 的子目录，其结构如下：
（这里名字数字开头不好，这里没办法凑合用，带数字导入依赖的时候无法直接导入，可以采用显式导入，但是这样的话点击模块无法跳转到对应模块，这个注意下）
.
└── my_fastapi
    ├── __init__.py
    ├── 37_fastapi_sql_database.py
    ├── 38_fastapi_sql_models.py
    ├── 39_fastapi_sql_schemas.py
    ├── 40_fastapi_sql_crud.py
    └── 41_fastapi_sql_main.py

该文件 __init__.py 只是一个空文件，但它告诉 Python my_fastapi 是一个包。

现在让我们看看每个文件/模块的作用。

安装 SQLAlchemy： pip install sqlalchemy

"""

#  todo 0、导入 SQLAlchemy 部件
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
import urllib.parse

# todo 1、为 SQLAlchemy 定义数据库 URL地址

# SQLALCHEMY_DATABASE_URL = "sqlite:///./sql_app.db" # sqlite的写法
# SQLALCHEMY_DATABASE_URL = "postgresql://user:password@postgresserver/db" #PGSQL的写法


# 数据库连接
username = 'root'
password = 'root'
host = '127.0.0.1'
port = 33061
dbname = 'bigdata_test'

# URL 编码密码
encoded_password = urllib.parse.quote_plus(password)

# 构建 SQLAlchemy 连接字符串（这里是MySQL的写法）
SQLALCHEMY_DATABASE_URL = f"mysql+pymysql://{username}:{encoded_password}@{host}:{port}/{dbname}"

# todo 2、创建 SQLAlchemy 引擎

# 第一步，创建一个 SQLAlchemy的“引擎”。
# 我们稍后会将这个engine在其他地方使用。
# 我这里是MySQL，对于SQLite，还要在下面加一个参数：connect_args={"check_same_thread": False}
"""
技术细节：
-----------
默认情况下，SQLite 只允许一个线程与其通信，假设有多个线程的话，也只将处理一个独立的请求。

这是为了防止意外地为不同的事物（不同的请求）共享相同的连接。

但是在 FastAPI 中，使用普通函数（def）时，多个线程可以为同一个请求与数据库交互，所以我们需要使用 connect_args={"check_same_thread": False} 来让 SQLite 允许这样。

此外，我们将确保每个请求都在依赖项中获得自己的数据库连接会话，因此不需要该默认机制。
"""

engine = create_engine(SQLALCHEMY_DATABASE_URL)

# todo 3、创建一个 SessionLocal 类
"""
创建一个 SessionLocal 类：
---------------------------
每个 SessionLocal 类的实例都会是一个数据库会话。当然该类本身还不是数据库会话。

但是一旦我们创建了一个 SessionLocal 类的实例，这个实例将是实际的数据库会话。

我们将它命名为 SessionLocal 是为了将它与我们从 SQLAlchemy 导入的 Session 区别开来。

稍后我们将使用 Session（从 SQLAlchemy 导入的那个）。

要创建 SessionLocal 类，请使用函数 sessionmaker。
"""

SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

# todo 4、创建一个Base类
# 现在我们将使用declarative_base()返回一个类。
# 稍后我们将继承这个类，来创建每个数据库模型或类（ORM 模型）
Base = declarative_base()
